--[[
===== Syntax Definition =====
[varname], square-brackets, variable
"fixedstring", double-quotes, a fixed string value
..., ellipsis, repeat 0 to N times


===== Data Structure =====
ConfigItem = {
	Key = "[keyname]",
	Type = "[type]",
	Value = "[value]",
	Condition = "[cond]" | nil,
	Target = "[unit]" | nil,
	TargetFrames = "[framname1], [framname2], ..." | nil,
	Comment = "[comment]" | nil,
}

UserItem = {
	Key = "[keyname]",
	Type = "[type]",
	Value = "[value]",
	Condition = "[cond]" | "nil",
	Target = "[unit]" | nil,
	TargetFrames = {1 = "[framname1]", 2 = "[framname2]", ...},
	Modifier = "[mod]-" | "",
	Button = [buttonnum] | nil
}

Config = {
	1 = {[ConfigItem1], [ConfigItem2], ...},
	2 = {[ConfigItem1], [ConfigItem2], ...},
	...
}

SelectedGroup = {
	1 = [groupindex1],
	2 = [groupindex2],
	...
}

UserConfig.Items = {
	1 = [UserItem1],
	2 = [UserItem2],
	...
}

InnerConfig.Items = {
	[targetframenameN] = {
		ConditionSecquence = {1 = "[condition1]", 2 = "[condition2]", ...},

		[condition1] = {
			[keyname1] = [UserItem1],
			[keyname2] = [UserItem2],
			...
		},

		[condition2] = {
			[keyname1] = [UserItem1],
			[keyname2] = [UserItem2],
			...
		},

		...
	},
}

Header.Attributes = { button[i] = "[buttonname]", ... }

Button.Attributes = {
	conds = "[conditions]" | nil,
	bakunit = "[original unit]" | nil,
	laststate = "[laststatevalue]" | nil,
	cond[i]-[count]-[varname] = "[varvalue]", ...
}

]]

ShortKey = {}
ShortKey.Setting = {
	IsAutoLoad = true,
	IsAutoSelect = true,
	IsShowSelected = false,
}
ShortKey.NILCONDITION = "nil"
ShortKey.CustomButton = { Items = {} }
ShortKey.UserConfig = { Items = {} }
ShortKey.InnerConfig = { Items = {} }
ShortKey.OutsideConfig = { SelectedGroup = {} }
ShortKey.InsideConfig = { SelectedGroup = {} }

local Setting = ShortKey.Setting
local NILCONDITION = ShortKey.NILCONDITION
local CustomButton = ShortKey.CustomButton
local UserConfig = ShortKey.UserConfig
local InnerConfig = ShortKey.InnerConfig

local MouseButtons = {
	LeftButton = 1,
	RightButton = 2,
	MiddleButton = 3,
	Button4 = 4,
	Button5 = 5,
	Button6 = 6,
	Button7 = 7,
	Button8 = 8,
	Button9 = 9,
	Button10 = 10,
	Button11 = 11,
	Button12 = 12,
	Button13 = 13,
	Button14 = 14,
	Button15 = 15,
}

local Modifiers = {
	CTRL = true,
	SHIFT = true,
	ALT = true,
}

------------------------------------------------------------------------------------------------------
--local functions
------------------------------------------------------------------------------------------------------

--return ismousebutton|nil, modifier|nil|"", mousebutton|nil
local function IsMouseButton(key)
	local modifier, dash, button = string.match(key, "(%a*)(-?)(.*Button.?)")

	if (dash ~= "-") then
		dash = nil
	end

	if (not dash and button == "Button") then
		button = key
	end

	if (not Modifiers[modifier]) then
		modifier = nil
	end

	if (not MouseButtons[button]) then
		button = nil
	end

	if (not modifier and not dash and button) then
		return true, "", MouseButtons[button]
	elseif (modifier and dash and button) then
		return true, string.lower(modifier).."-", MouseButtons[button]
	end
end

------------------------------------------------------------------------------------------------------
--Interface
------------------------------------------------------------------------------------------------------

function ShortKey.ErrorReport()
	print("error")
end

--interface
function CustomButton:Create()
	local index = table.getn(self.Items) + 1
	local buttonname = "ShortKeyActionButton"..index
	table.insert(self.Items, buttonname)
	return buttonname
end

--interface
function UserConfig:IsGroupLoad(group)
	return true
end

local ErrorReport = ShortKey.ErrorReport

------------------------------------------------------------------------------------------------------
--Setting, OutsideConfig, InsideConfig
------------------------------------------------------------------------------------------------------
local function SelectedGroupToString(config)
	if (not config.SelectedGroup or #config.SelectedGroup == 0) then
		return "empty"
	end

	return table.concat(config.SelectedGroup, ",")
end

ShortKey.OutsideConfig.ToString = SelectedGroupToString
ShortKey.InsideConfig.ToString = SelectedGroupToString

function Setting:ToString()
	return string.format("AutoLoad: %s AutoSelect: %s", tostring(self.IsAutoLoad), tostring(self.IsAutoSelect))
end

------------------------------------------------------------------------------------------------------
--UserConfig
------------------------------------------------------------------------------------------------------

function UserConfig:LoadManual(config)
	for _, index in ipairs(config.SelectedGroup) do
		local group = config[index]

		if (not group) then
			ErrorReport(nil, nil, string.format("Config group [%s] not exist, please clear your setting.", index))
			return false
		end

		for _, useritem in ipairs(group) do
			table.insert(self.Items, useritem)
		end
	end
end

function UserConfig:LoadAuto(config)
	local isload = false
	table.wipe(config.SelectedGroup)

	for index, group in ipairs(config) do
		if (not group.IsLoad) then
			isload = self:IsGroupLoad(group)
		else
			isload = group:IsLoad()
		end

		if (isload) then
			table.insert(config.SelectedGroup, index)

			for _, useritem in ipairs(group) do
				table.insert(self.Items, useritem)
			end
		end
	end
end

function UserConfig:ItemInit(useritem)
	--check Key exist, must have it
	if (not useritem.Key) then
		ErrorReport(nil, useritem, "Key not exist")
		return false
	end

	--init to set mouse button
	local ismouse, modifier, button = IsMouseButton(useritem.Key)
	useritem.Modifier = modifier
	useritem.Button = button

	--init to parse TargetFrames to table type
	if (type(useritem.TargetFrames) == "string") then
		local frames = {}
		local framename

		for i = 1, select("#", strsplit(",", useritem.TargetFrames)) do
			framename = select(i,strsplit(",", useritem.TargetFrames))
			table.insert(frames, strtrim(framename));
		end

		useritem.TargetFrames = frames
	elseif (type(useritem.TargetFrames) == "table") then
	else
		useritem.TargetFrames = nil
	end

	--check mouse key must be pointed a TargetFrame
	if (useritem.TargetFrames == nil and ismouse) then
		ErrorReport(nil, useritem, "TargetFrames not exist when Key is mouse button")
		return false
	end

	--init to set default value to condition if nil
	useritem.Condition = useritem.Condition or NILCONDITION

	--init to set default value to comment if nil
	--useritem.Comment = useritem.Comment or ""

	return true
end

function UserConfig:ToString(useritem)
	local targetframes = useritem.TargetFrames

	if (useritem.TargetFrames and type(useritem.TargetFrames) == "table") then
		targetframes = table.concat(useritem.TargetFrames, ",")
	end

	return string.format("Key:%s Type:%s Value:%s Target:%s Condition:%s TargetFrames:%s",
				useritem.Key  or "nil",
				useritem.Type or "nil",
				useritem.Value or "nil",
				useritem.Target or "nil",
				useritem.Condition or "nil",
				targetframes or "nil")
end

------------------------------------------------------------------------------------------------------
--InnerConfig
------------------------------------------------------------------------------------------------------

function InnerConfig:ItemInit(useritem)
	--check mouse key conflict
	if (useritem.Button) then
		for _, framename in ipairs(useritem.TargetFrames) do
			local frameitem = self.Items[framename]

			if (frameitem) then
				local conditem = frameitem[useritem.Condition]

				if (conditem and conditem[useritem.Key]) then
					ErrorReport(conditem[useritem.Key], useritem, "Mouse button conflict")
					return false
				end
			end
		end

	--is keyboard key
	else
		--check keyboard key conflict
		for framename, frameitem in pairs(self.Items) do
			local conditem = frameitem[useritem.Condition]

			if (conditem and conditem[useritem.Key]) then
				ErrorReport(conditem[useritem.Key], useritem, "Key conflict")
				return false
			end
		end

		--init useritem.TargetFrames value
		if (not useritem.TargetFrames) then
			local targetframe = nil

			--find an exist one
			for _, framename in ipairs(CustomButton.Items) do
				local frameitem = self.Items[framename]

				if (frameitem) then
					for _, conditem in pairs(frameitem) do
						if (conditem[useritem.Key]) then
							targetframe = framename
						end
					end
				end
			end

			--if not found, create one
			if (not targetframe) then
				targetframe = CustomButton:Create()
			end

			useritem.TargetFrames = {targetframe}
		end
	end

	return true
end


function InnerConfig:ItemImport(useritem)
	for _, framename in ipairs(useritem.TargetFrames) do
		local frameitem = self.Items[framename] or {}
		frameitem.ConditionSequence = frameitem.ConditionSequence or {}

		local conditem = frameitem[useritem.Condition]

		--dup condition only hold one place
		if (not conditem) then
			conditem = {}

			if (useritem.Condition ~= NILCONDITION) then
				table.insert(frameitem.ConditionSequence, useritem.Condition)
			end
		end

		conditem[useritem.Key] = useritem
		frameitem[useritem.Condition] = conditem

		self.Items[framename] = frameitem
	end
end

function InnerConfig:Import(userconfig)
	for _, useritem in ipairs(userconfig.Items) do
		if (userconfig:ItemInit(useritem) and self:ItemInit(useritem)) then
			self:ItemImport(useritem)
		end
	end
end